/**
 * model my real bluetooth device
 *
 * must be init'ed / constructed by LocalDevice.getLocalDevice()
 * it is a runnable object with listener capability
 *
 * TODO reverse searching on list
 * 
 * Usage:
 * <code>
 * MyBluetooth myBluetooth;
 * // peter: actually LocalDevice.getLocalDevice() is not needed
 * myBluetooth = new MyBluetooth(LocalDevice.getLocalDevice())
 * myBluetooth.setListener(newDeviceListener); // 
 * // TODO: myBluetooth.setRequireService(new UUID[] {UUID_OBEX_PUSH});
 * while (running) {
 *     myBluetooth.startInquiry();
 *     myBluetooth.waitInquiry(); //this is blocking
 *     myBluetooth.startServiceSearch();  // blocking!
 * }
 *
 * interface NewDeviceListener:
 * int deviceFound(RemoteDevice dev, Capability)
 * 
 * </code>
 */

import javax.bluetooth.*;
import java.util.Vector;
import java.util.Hashtable;


public class MyBluetooth implements DiscoveryListener
{
    private LocalDevice device;  // the real LocalDevice

    public String friendlyName = "[null]";
    public String address = "[null]";

    public Vector<RemoteDevice> remoteDeviceList = new Vector<RemoteDevice>();
    public Vector<RemoteDevice> obexDeviceList = new Vector<RemoteDevice>();
    private DiscoveryAgent agent;

    private boolean inquiryDone = false;

	private final static UUID UUID_OBEX_PUSH = new UUID(0x1105);
    private final static UUID[] DEFAULT_UUID_LIST 
                                = new UUID[] { UUID_OBEX_PUSH };
    private UUID[] reqUUIDList = DEFAULT_UUID_LIST;

    // property related
    public String stack = "[null]";
    public int maxServiceDiscovery = 1;

    // temp for service discovery
    Hashtable<Integer,RemoteDevice> deviceHash = 
            new Hashtable<Integer,RemoteDevice>();


    public MyBluetooth(LocalDevice _device) {
        device = _device;

        try { 
            address = device.getBluetoothAddress();
            friendlyName = device.getFriendlyName(); 
            stack = LocalDevice.getProperty("bluecove.stack");
            System.out.println("bluecove.stack:" + stack);
            String str = LocalDevice.getProperty("bluetooth.sd.trans.max");
            maxServiceDiscovery = Integer.parseInt(str);

            System.out.println("-- maxServiceDiscovery=" + maxServiceDiscovery);

        } catch (Exception e) { 
            System.err.println("MyBluetooth exception: " + e);
        }
    }

// 4 interfaces that must be implemented (DiscoveryListener)
// void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) 
// void inquiryCompleted(int discType) 
// void servicesDiscovered(int transID, ServiceRecord[] servRecord) 
// void serviceSearchCompleted(int transID, int respCode) 

    public boolean startInquiry() 
    {
        try {
            inquiryDone = false;
            agent = device.getDiscoveryAgent();
            agent.startInquiry(DiscoveryAgent.GIAC, this);
        } catch (Exception e) {
            System.err.println("startInquiry ex: " + e);
            inquiryDone = true;
            return false;
        }
        return true;
    }

    public boolean waitInquiry() {
        try {
            System.out.println("<<< before waitInquiry");
            synchronized(this) {
                if (inquiryDone==true) { 
                    return true;
                }
                wait(30000);  // this is a timeout
            }
            System.out.println(">>> after waitInquiry");
            return inquiryDone;
        } catch (Exception e) {
            System.err.println("waitInquiry ex: " + e);
            return inquiryDone; // error!
        }
    }

    public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod)  {
        System.out.println("--- deviceDiscovered  address:" 
        + btDevice.getBluetoothAddress());
        
        /*** this is useless and slow 
        try { 
            String name = btDevice.getFriendlyName(true);
            System.out.println("     name:" + name);
        } catch (Exception e) {
            System.err.println("ex on name:" + e);
        }
        *****/

        if (remoteDeviceList.contains(btDevice)==true) {
            System.out.println("duplicated device"); // may not a bug
            return;
        }

        // consider: hashtable
        // key=bluetooth address 
        // value=btDevice (RemoteDevice)
        remoteDeviceList.addElement(btDevice);
    }


    public void inquiryCompleted(int discType)  {
        System.out.println("--- inquiryCompleted ---");
        synchronized(this) {
            inquiryDone = true;
            notify();
        }
    }

	public void startServiceSearch()  {
        int MAX_RETRY = 5;
        int transID = -1;
        // TODO: reverse the order of list (newer device usually alive!)
        for (RemoteDevice btDevice : remoteDeviceList) {

            for (int i=0; i<MAX_RETRY; i++) {
                try {
                    transID = agent.searchServices(null, 
                        reqUUIDList,
                        // new UUID[] {UUID_OBEX_PUSH}, 
                        btDevice, this);
                    // @see servicesDiscovered
                    // peter: bug may have race condition!
                    deviceHash.put(new Integer(transID), btDevice);

                    System.out.println("-- service search start, transID=" 
                         + transID);

                    break;  // normal, search began

                } catch (BluetoothStateException e) {
                    System.err.println("startServiceSearch full: " + e);
                    try {
                        long startTime = System.currentTimeMillis();
                        synchronized(this) {
                            wait(10000);  // wait 10 seconds
                        }
                        System.out.println("waited: " + 
                            (System.currentTimeMillis() - startTime) +
                            " transID:" + transID);
                    } catch (Exception ee) {
                        System.err.println("startServiceSearch ex2: " + ee);
                    }
                } catch (Exception e) {
                    // this is real error
                    System.err.println("startServiceSearch ex: " + e);
                    break;
                }
            }
        }

	    // dev.setTransactionID(transID);
		// else serviceSearchQueue.add(btDevice);
	}


    public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
        System.out.println("@@@: servicesDiscovered transID=" + transID);

        Integer key = new Integer(transID);
        RemoteDevice btDevice = deviceHash.get(key);
        obexDeviceList.addElement(btDevice);
        deviceHash.remove(key);
        System.out.print("***** obex push ready,  address:" 
            + btDevice.getBluetoothAddress());

        String name = "[unknown]";
        try { 
            name = btDevice.getFriendlyName(false);
        } catch (Exception e) { }
        System.out.println("  name:" + name);

    }

    public void serviceSearchCompleted(int transID, int respCode)  {
        System.out.println("TODO: serviceSearchCompleted transID=" + transID
        + "  respCode=" + respCode);
        synchronized(this) {
            notify();  // pair with wait(xxx) in startServiceSearch()
        }
    }
}

